package jannex.mapping;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;

public class Cases {

	public static CaseConverter capitalize() {
		return new CapitalizeConverter();
	}

	public static CaseConverter capitalize(boolean firstIsLower) {
		return new CapitalizeConverter(firstIsLower);
	}

	
	public static CaseConverter id() {
		return new CaseConverter() {
			@Override
			public String convert(String inCase) {
				return inCase;
			}
		};
	}

	
	public static CaseConverter upper() {
		return new CaseConverter() {
			@Override
			public String convert(String notInCase) {
				return notInCase.toUpperCase(Locale.ENGLISH);
			}
		};
	}

	
	public static CaseConverter lower() {
		return new CaseConverter() {
			@Override
			public String convert(String notInCase) {
				return notInCase.toLowerCase(Locale.ENGLISH);
			}
		};
	}
	
	
	/**
	 * From: Snaked_Case -> SnakedCase
	 * From: _Snaked_Case -> SnakedCase
	 * From: ABC_Snaked_Case -> ABCSnakedCase
	 * From: Abc_Snaked_Case -> AbcSnakedCase
	 * To: SnakedCase -> Snaked_Case
	 * To: ABCSnakedCase -> ABC_Snaked_Case
	 */
	public static CaseConverter snake() {

		return new CaseConverter() {

		    @Override
		    public String convert(String notInCase) {
		        StringBuilder out = new StringBuilder();
		        for (int ix = 0; ix < notInCase.length(); ix++) {
		            char c = notInCase.charAt(ix);
		            if (out.length() != 0 &&
		                    ix + 1 < notInCase.length() &&
		                    (Character.isUpperCase(c) || Character.isDigit(c)) &&
		                    Character.isLowerCase(notInCase.charAt(ix + 1))) {
		                out.append('_');
		            } else if (out.length() != 0 &&
		                    ix > 0 &&
		                    (Character.isUpperCase(c) || Character.isDigit(c)) &&
		                    Character.isLowerCase(notInCase.charAt(ix - 1))) {
		                out.append('_');
		            }
		            out.append(c);
		        }
		        return out.toString();
		    }
		};
	}
	
	
	public static CaseConverter filter(String regex) {
		return new FilterConverter(regex);
	}
	
	
	public static CaseConverter chain(List<CaseConverter> converters) {
		return new ChainConverter(converters);
	}
	
	public static CaseConverter chain(CaseConverter... converters) {
		return new ChainConverter(Arrays.asList(converters));
	}
	
	
	private static class ChainConverter implements CaseConverter {

	    private List<CaseConverter> converters = new ArrayList<>();

	    public ChainConverter(List<CaseConverter> converters) {
	    	this.converters = converters;
	    }

	    @Override
	    public String convert(String inCase) {
	        String result = inCase;
	        for (CaseConverter converter : converters) {
	            result = converter.convert(result);
	        }
	        return result;
	    }
	}
	
	private static class FilterConverter implements CaseConverter {

	    private final Pattern pattern;

	    public FilterConverter(String regex) {
	    	pattern = Pattern.compile(regex);
	    }

	    @Override
	    public String convert(String inCase) {
	        return pattern.matcher(inCase).replaceAll("");
	    }
	}

	private static class CapitalizeConverter implements CaseConverter {

		private boolean firstIsLower;

		public CapitalizeConverter(boolean firstIsLower) {
			this.firstIsLower = firstIsLower;
		}

		public CapitalizeConverter() {
			this(false);
		}

		@Override
		public String convert(String notInCase) {
			StringBuilder out = new StringBuilder();
			boolean wordStart = true;
			for (int ix = 0; ix < notInCase.length(); ix++) {
				char c = notInCase.charAt(ix);
				if (ix == 0 && firstIsLower) {
					out.append(Character.toLowerCase(c));
					wordStart = false;
				} else if (wordStart) {
					out.append(Character.toUpperCase(c));
					wordStart = false;
				} else if (c == '_' || c == ' ') {
					out.append(c);
					wordStart = true;
				} else {
					out.append(Character.toLowerCase(c));
				}
			}
			return out.toString();
		}
	}

}
